#include "config.h"

#include <clx/libECM.h>

#include "libms2sim.h"

#define _XOPEN_SOURCE	600
#include <fcntl.h>


#define BLKSIZE	32768
#define	NBLOCKS	8192

#define TESTFILE	"testfile"

static ecm_uint32	blksize = BLKSIZE;
static ecm_uint32	nblocks = NBLOCKS;
static ecm_uint64	testfile_size;
static ecm_uint32	n_threads = 1;

static ecm_double	*BWs;
static ECMBOOL		existing_mode;

static char		*filelist_name;
static ecm_ptrs_t	*testfile_names;

static void
usage(void)
{
	ecm_fprintf(ecm_stderr,
"Usage: dperf [ <options> ]\n"
" Options:\n"
"   -h: help\n"
"   -b <blksize>: block size(default: 32768)\n"
"   -n <block count>\n"
"   -s <test file size>\n"
"   -t <# of threads>\n"
"   -f <testfile list>: use existing file as testfile\n"
	);
}

static ECMBOOL
create_testfile(ecm_uint32 idx)
{
	char	*fname;
	char	*buf;
	ecm_uint32	i;
	int	fd;

	buf = ecm_malloc(blksize);
	ECMASSERT(buf != NULL);

	fname = (char *)ecm_ptrs_get(testfile_names, idx);
	if (ecm_creat(&fd, fname, ECM_P_ALLU) != ECM_OK) {
		ecm_free(buf);
		ECMERR("cannot create test file");
		return ECM_FALSE;
	}

	posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);

	for (i = 0; i < nblocks; i++) {
		ecm_size_t	size = blksize;
		ecm_write(fd, buf, &size);
	}
	ecm_fsync(fd);
	ecm_close(fd);

	ecm_free(buf);

	return ECM_TRUE;
}

static ECMBOOL
read_testfile(ecm_uint32 idx, ECMBOOL simple_load)
{
	char	*fname;
	char	*buf;
	ecm_uint32	i;
	ecm_uint64	starttick, duration;
	int	fd;

	buf = ecm_malloc(blksize);
	ECMASSERT(buf != NULL);

	fname = (char *)ecm_ptrs_get(testfile_names, idx);
	fd = ecm_open(fname, ECM_O_RDONLY);
	if (fd < 0) {
		ecm_free(buf);
		ECMERR("cannot open test file");
		ecm_exit(3);
	}

	posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);

	starttick = ecm_tickcount();
	for (i = 0; i < nblocks; i++) {
		ecm_size_t	size = blksize;
		ecm_read(fd, buf, &size);
	}
	duration = ecm_tickcount() - starttick;

	if (!simple_load)
		BWs[idx - 1] = (1.0 * blksize * nblocks) / duration * 1000;

	ecm_close(fd);
	ecm_free(buf);

	if (!simple_load && !existing_mode) {
		ecm_unlink(fname);
	}

	return ECM_TRUE;
}

static void
display_bandwidth(ecm_double BW)
{
	static const char	*units[] = {
		"B/sec", "KB/sec", "MB/sec", "GB/sec", "TB/sec", NULL
	};
	ecm_uint32	i;

	for (i = 0; units[i + 1]; i++, BW /= 1024) {
		if (BW < 1024)
			break;
	}
	ecm_fprintf(ecm_stdout, "%.2f%s\n", BW, units[i]);
}

static void
func(ecm_ptr_t ctx)
{
	ecm_uint32	idx = (ecm_uint32)(ecm_iptr_t)ctx;

	read_testfile(idx, ECM_FALSE);
}

static void
run_dperf(void)
{
	ecm_thread_t	*threads;
	ecm_uint32	i;
	ecm_double	BW = 0;

	ECMVERBOSE("%u reads performance test", n_threads);

	for (i = 0; i < n_threads; i++) {
		if (!existing_mode) {
			if (!create_testfile(i + 1))
				ecm_exit(2);
		}
		else {
			read_testfile(i + 1, ECM_TRUE);
		}
	}

	threads = (ecm_thread_t *)ecm_malloc(sizeof(ecm_thread_t) * n_threads);
	BWs = (ecm_double *)ecm_calloc(n_threads, sizeof(ecm_thread_t));
	for (i = 0; i < n_threads; i++) {
		ecm_thread_create(&threads[i], func, (ecm_ptr_t)(ecm_iptr_t)(i + 1));
	}

	for (i = 0; i < n_threads; i++) {
		ecm_thread_free(threads[i]);
		BW += BWs[i];
	}

	display_bandwidth(BW);

	ecm_free(BWs);
	ecm_free(threads);
}

static void
load_names_from_file(void)
{
	FILE	*fp;
	char	buf[1024];
	ecm_uint32	i;

	if ((fp = ecm_fopen(filelist_name, "r")) == NULL) {
		ECMERR("%s: cannot open", filelist_name);
		ecm_exit(3);
	}

	while (ecm_fgets(buf, 1024, fp) > 0) {
		int	fd;
		ecm_size_t	size;

		if (ecm_empty_str(buf) || buf[0] == '#')
			continue;
		ecm_trim(buf);
		fd = ecm_open(buf, ECM_O_RDONLY);
		if (fd < 0) {
			ECMERR("%s: cannot open", buf);
			ecm_exit(3);
		}
		ecm_filesize(fd, &size);
		if (size < testfile_size / n_threads) {
			ECMERR("%s: smaller than testfile size", buf);
			ecm_exit(3);
		}

		ecm_ptrs_add(testfile_names, ecm_strdup(buf));

	}
	ecm_fclose(fp);

	if (testfile_names->count < n_threads) {
		ECMERR("testfile is smaller than # of threads");
		ecm_exit(3);
	}

	for (i = 0; i < n_threads; i++) {

	}
}

static void
load_testfile_names(void)
{
	testfile_names = ecm_ptrs_new(16);

	if (!existing_mode) {
		ecm_uint32	i;

		for (i = 0; i < n_threads; i++) {
			char	*fname;

			ecm_asprintf(&fname, "%s_%u", TESTFILE, i);
			ecm_ptrs_add(testfile_names, fname);
		}
		return;
	}
	load_names_from_file();
}
		
static void
parse_args(int argc, char *argv[])
{
	int	c;

	while ((c = ecm_getopt(argc, argv, "hs:b:n:t:f:")) != -1) {
		switch(c) {
		case 'h':
			usage();
			ecm_exit(0);
		case 'b':
			if (ecm_sscanf(ecm_optarg, "%u", &blksize) != 1) {
				ECMERR("%s: invalid block size", blksize);
				ecm_exit(1);
			}
			break;
		case 'n':
			if (ecm_sscanf(ecm_optarg, "%u", &nblocks) != 1) {
				ECMERR("%s: invalid number of blocks", nblocks);
				ecm_exit(1);
			}
			break;
		case 's':
			if (!msl_parse_bandwidth(ecm_optarg, &testfile_size)) {
				ECMERR("%s: invalid testfile size", nblocks);
				ecm_exit(1);
			}
			break;
		case 't':
			if (!ecm_sscanf(ecm_optarg, "%u", &n_threads) || n_threads == 0) {
				ECMERR("%s: invalid # of threads", n_threads);
				ecm_exit(1);
			}
			break;
		case 'f':
			filelist_name = ecm_strdup(ecm_optarg);
			existing_mode = ECM_TRUE;
			break;
		default:
			usage();
			ecm_exit(1);
		}
	}
}

int
main(int argc, char *argv[])
{
	ECM_initialize();

	parse_args(argc, argv);

	ecm_set_output_file(ecm_stderr);
	ecm_set_verbose_mode(ECM_TRUE);

	if (testfile_size > 0) {
		nblocks = testfile_size / blksize / n_threads;
	}

	testfile_size = nblocks * blksize;

	load_testfile_names();

	run_dperf();

	return 0;
}
